package com.sun.services.users.impls;

import cn.hutool.core.util.StrUtil;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Wildcard;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.sun.commons.base.BaseService;
import com.sun.commons.base.R;
import com.sun.commons.utils.JwtTokenUtil;
import com.sun.commons.utils.QueryDslOrderUtil;
import com.sun.dto.BaseSearchModel;
import com.sun.dto.users.LoginDto;
import com.sun.dto.users.RoleDto;
import com.sun.dto.users.UserDto;
import com.sun.dto.users.UserSearchModel;
import com.sun.entitys.users.QUser;
import com.sun.entitys.users.User;
import com.sun.services.roles.repository.RoleRepository;
import com.sun.services.users.UserMapper;
import com.sun.services.users.repository.UserRepository;
import com.sun.services.users.service.UserService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Optional;

/**
 * MP自动填充Handler
 *
 * @Author sun
 * @CreateTime 2025/9/6 21:32
 */
@Slf4j
@Service
@AllArgsConstructor
public class UserServiceImpl extends BaseService implements UserService {

    private final ApplicationEventPublisher event;
    private final UserRepository userRepository;
    private final RoleRepository roleRepository;
    private final UserMapper mapper;
    private final PasswordEncoder passwordEncoder;
    private final JwtTokenUtil jwtTokenUtil;
    private final JdbcAggregateTemplate jdbcAggregateTemplate;
    private final JPAQueryFactory queryFactory;

    /**
     * 根据用户ID或用户名查询用户
     *
     * @param id       用户ID
     * @param username 用户名
     * @return 用户实体
     */
    private User getUserByIdOrUsername(Long id, String username) {
        if (id != null) {
            return userRepository.findById(id).orElse(null);
        } else if (username != null) {
            UserDto userDto = userRepository.findByUsername(username);
            return userDto != null ? userRepository.findById(userDto.id()).orElse(null) : null;
        }
        return null;
    }

    /**
     * 将Tuple转换为UserDto
     *
     * @param tuple 查询结果
     * @param qUser 查询实体
     * @return 用户数据传输对象
     */


    /**
     * 加密密码
     *
     * @param password 明文密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    @Override
    public R Login(LoginDto loginDto) {
        if (StrUtil.isBlank(loginDto.username()) || StrUtil.isBlank(loginDto.password())) {
            return R.fail("用户名或密码不能为空");
        }
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        UserDto user = userRepository.findByUsername(loginDto.username());
        if (user == null) {
            return R.fail("用户不存在");
        }


        if (!encoder.matches(loginDto.password(), user.password())) {
            return R.fail("密码错误");
        }
        List<RoleDto> roles = roleRepository.findByUserId(user.id());
        user.roles().addAll(roles);
        String token = jwtTokenUtil.generateToken(user);
        return R.ok("登录成功").setData(token);

    }

    @Transactional
    @Override
    public R register(UserDto userDto) {
        if (StrUtil.isBlank(userDto.username()) || StrUtil.isBlank(userDto.password())) {
            return R.fail("用户名或密码不能为空");
        }
        User user = getUserByIdOrUsername(null, userDto.username());
        if (user != null) {
            return R.fail("用户已存在");
        }

        User entity = mapper.toEntity(userDto);
        entity.setPassword(encodePassword(userDto.password()));
        entity.setId(nextId());
        userRepository.save(entity);
        return R.ok("注册成功");
    }
    @Transactional
    @Override
    public R update(UserDto userDto) {
        User user = getUserByIdOrUsername(userDto.id(), null);
        if (user == null) {
            return R.fail("用户不存在");
        }
        if (StrUtil.isBlank(userDto.name())) {
            return R.fail("用户名不能为空");
        }
        user = mapper.toEntity(userDto, user);

        userRepository.save(user);
        return R.ok("修改成功");
    }

    @Override
    public R delete(Long[] id) {
        return null;
    }

    @Transactional
    @Override
    public R updatePassword(UserDto userDto) {
        User user = getUserByIdOrUsername(userDto.id(), null);

        if (user == null) {
            return R.fail("用户不存在");
        }
        user.setPassword(encodePassword(userDto.password()));
        userRepository.save(user);
        return R.ok("修改成功");
    }
    @Transactional
    @Override
    public R resetPassword(UserDto userDto) {
        User user = getUserByIdOrUsername(userDto.id(), null);
        if (user == null) {
            return R.fail("用户不存在");
        }
        user.setPassword(passwordEncoder.encode("000000"));
        userRepository.save(user);
        return R.ok("密码已重置为：000000");
    }

    @Override
    public List<UserDto> getUserList(UserSearchModel searchModel) {
        QUser qUser = QUser.user;
        Predicate predicate = buildPredicate(qUser, searchModel);
        //Page<User> actorPage = userRepository.findAll(predicate);

        return buildQuery(qUser, predicate, searchModel)
                .fetch().stream()
                .map(tuple -> convertTupleToUserDto(tuple, qUser))
                .toList();
    }

    @Override
    public Page<UserDto> getUserListByPage(UserSearchModel searchModel) {
        QUser qUser = QUser.user;
        Predicate predicate = buildPredicate(qUser, searchModel);

        long total = Optional.ofNullable(queryFactory.select(Wildcard.count)
                        .from(qUser)
                        .where(predicate)
                        .fetchOne())
                .orElse(0L);

        JPAQuery<Tuple> query = buildQuery(qUser, predicate, searchModel);
        Pageable pageable = PageRequest.of(searchModel.getPage() - 1, searchModel.getSize());
        List<UserDto> list = query.offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch().stream().map(tuple -> convertTupleToUserDto(tuple, qUser))
                .toList();

        return new PageImpl<>(list, pageable, total);

    }

    private UserDto convertTupleToUserDto(Tuple tuple, QUser qUser) {
        return new UserDto(
                tuple.get(qUser.id),
                tuple.get(qUser.name),
                tuple.get(qUser.code),
                tuple.get(qUser.description),
                tuple.get(qUser.remark),
                Boolean.TRUE.equals(tuple.get(qUser.enabled)),
                tuple.get(qUser.username),
                tuple.get(qUser.password),
                tuple.get(qUser.phone),
                tuple.get(qUser.openid),
                tuple.get(qUser.type),
                tuple.get(qUser.createTime),
                tuple.get(qUser.updateTime),
                tuple.get(qUser.createrName),
                tuple.get(qUser.updaterName),
                tuple.get(qUser.createrId),
                tuple.get(qUser.updaterId)
        );
    }

    private JPAQuery<Tuple> buildQuery(QUser qUser, Predicate predicate, BaseSearchModel searchModel) {
        JPAQuery<Tuple> query = queryFactory.select(qUser.id, qUser.name, qUser.code,
                        qUser.description, qUser.remark, qUser.enabled, qUser.username,
                        qUser.password, qUser.phone, qUser.openid, qUser.type, qUser.createTime,
                        qUser.updateTime, qUser.createrName, qUser.updaterName, qUser.createrId,
                        qUser.updaterId)
                .from(qUser)
                .where(predicate);
        if (StrUtil.isEmpty(searchModel.getOrderBy())) {
            query.orderBy(qUser.createTime.desc());
        } else {
            query.orderBy(QueryDslOrderUtil.orderByField(qUser, searchModel.getOrderBy(), searchModel.isAsc()));

        }
        return query;
    }

    private Predicate buildPredicate(QUser qUser, UserSearchModel searchDto) {

        Predicate predicate = getPredicates(qUser, searchDto);
        if (!StrUtil.isEmpty(searchDto.getUsername())) {
            predicate = ExpressionUtils.and(predicate, qUser.username.eq(searchDto.getUsername()));
        }
        if (!StrUtil.isEmpty(searchDto.getPhone())) {
            predicate = ExpressionUtils.and(predicate, qUser.phone.eq(searchDto.getPhone()));
        }
        if (!StrUtil.isEmpty(searchDto.getOpenid())) {
            predicate = ExpressionUtils.and(predicate, qUser.openid.eq(searchDto.getOpenid()));
        }
        if (searchDto.getType() != null) {
            predicate = ExpressionUtils.and(predicate, qUser.type.eq(searchDto.getType()));
        }
        return predicate;
    }


    private Predicate getPredicates(QUser entity, BaseSearchModel searchDto) {
        Predicate predicate = entity.deleted.eq(0L);

        if (searchDto.getEnabled() != null) {
            predicate = ExpressionUtils.and(predicate, entity.enabled.eq(searchDto.getEnabled()));
        }
        if (StrUtil.isNotEmpty(searchDto.getName())) {
            predicate = ExpressionUtils.and(predicate, entity.name.like("%" + searchDto.getName() + "%"));
        }
        if (StrUtil.isNotEmpty(searchDto.getCode())) {
            predicate = ExpressionUtils.and(predicate, entity.code.like("%" + searchDto.getCode() + "%"));
        }
        if (StrUtil.isNotEmpty(searchDto.getRemark())) {
            predicate = ExpressionUtils.and(predicate, entity.remark.like("%" + searchDto.getRemark() + "%"));
        }
        if (StrUtil.isNotEmpty(searchDto.getCreaterName())) {
            predicate = ExpressionUtils.and(predicate, entity.createrName.like("%" + searchDto.getCreaterName() + "%"));
        }
        if (StrUtil.isNotEmpty(searchDto.getUpdaterName())) {
            predicate = ExpressionUtils.and(predicate, entity.updaterName.like("%" + searchDto.getUpdaterName() + "%"));
        }
        if (searchDto.getCreateTime() != null) {
            predicate = ExpressionUtils.and(predicate, entity.createTime.eq(searchDto.getCreateTime()));
        }
        if (searchDto.getUpdateTime() != null) {
            predicate = ExpressionUtils.and(predicate, entity.updateTime.eq(searchDto.getUpdateTime()));
        }

        if (searchDto.getCreaterId() != null) {
            predicate = ExpressionUtils.and(predicate, entity.createrId.eq(searchDto.getCreaterId()));
        }
        if (searchDto.getUpdaterId() != null) {
            predicate = ExpressionUtils.and(predicate, entity.updaterId.eq(searchDto.getUpdaterId()));
        }
        if (searchDto.getDeleted() != null) {
            predicate = ExpressionUtils.and(predicate, entity.deleted.eq(searchDto.getDeleted()));
        }
        if (searchDto.getMinCreateTime() != null && searchDto.getMaxCreateTime() != null) {
            predicate = ExpressionUtils.and(predicate, entity.createTime.between(searchDto.getMinCreateTime(), searchDto.getMaxCreateTime()));
        }
        if (searchDto.getMinUpdateTime() != null && searchDto.getMaxUpdateTime() != null) {
            predicate = ExpressionUtils.and(predicate, entity.updateTime.between(searchDto.getMinUpdateTime(), searchDto.getMaxUpdateTime()));
        }

        return predicate;
    }

}
